package com.example.demo.aop;

import com.example.demo.annotate.Idempotent;
import com.example.demo.common.Func;
import com.example.demo.common.RedisService;
import com.example.demo.common.Valid;
import com.example.demo.common.constant.RedisKey;
import com.example.demo.common.constant.ResultCode;
import com.example.demo.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;

@Aspect
@Component
@Order(103)
@RequiredArgsConstructor
@Slf4j
public class IdempotentAop {
    private final RedisTemplate<String, String> redis;
    private final RedisService redisService;

    @Pointcut("@annotation(com.example.demo.annotate.Idempotent)")
    public void IdempotentAop() { }

    @Around("IdempotentAop()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取幂等性注解
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);

        // 获取幂等性校验值的key
        HttpServletRequest request = Func.getHttpRequest();
        String idempotentKey = request.getHeader(idempotent.header());
        if (Valid.isEmpty(idempotentKey)) {
            log.info(ResultCode.IDEMPOTENT_KEY_EMPTY.getMessage());
            throw new BusinessException(ResultCode.IDEMPOTENT_KEY_EMPTY);
        }

        // 检查idempotentKey是否存在redis里
        String idempotentValue = redis.opsForValue().get(idempotentKey);
        if (Valid.isEmpty(idempotentValue)) {
            log.info(ResultCode.IDEMPOTENT_VALUE_EMPTY.getMessage());
            throw new BusinessException(ResultCode.IDEMPOTENT_VALUE_EMPTY);
        }

        // 使用idempotentValue加锁
        String lockKey = RedisKey.IDEMPOTENT + idempotentValue;
        boolean isLock = redisService.getLock(lockKey, RedisKey.DEFAULT_LOCK_VALUE, Duration.ofSeconds(3L));
        if (!isLock) {
            log.info(ResultCode.IDEMPOTENT_REPEAT_SUBMIT.getMessage());
            throw new BusinessException(ResultCode.IDEMPOTENT_REPEAT_SUBMIT);
        }
        try {
            // 继续执行接口逻辑
            Object result = joinPoint.proceed();

            // 任务执行成功，清除redis中的idempotentKey缓存
            redis.delete(idempotentKey);
            return result;
        }
        catch (Exception ex) {
            log.error(ex.getMessage());
            throw new Exception();
        }
        finally {
            // 执行完毕之后，释放锁
            redisService.releaseLock(lockKey, RedisKey.DEFAULT_LOCK_VALUE);
        }
    }
}
